package com.yunkeji.dao.mapper;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.baomidou.lock.executor.RedissonLockExecutor;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yunkeji.common.constant.CacheConstants;
import com.yunkeji.common.core.domain.dto.PayLogDTO;
import com.yunkeji.common.core.domain.event.PayLogEvent;
import com.yunkeji.common.core.mapper.BaseMapperPlus;
import com.yunkeji.common.enums.app.BalanceTypeEnums;
import com.yunkeji.common.enums.app.EnableEnum;
import com.yunkeji.common.enums.app.PayLogTypeEnums;
import com.yunkeji.common.exception.ServiceException;
import com.yunkeji.dao.domain.AppUser;
import com.yunkeji.dao.domain.CallSheetInfo;
import com.yunkeji.dao.vo.AppUserVo;
import com.yunkeji.dao.vo.MonthStatisticsVo;
import com.yunkeji.dao.vo.broker.IndexVo;
import lombok.Data;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

/**
 * 用户基本信息Mapper接口
 *
 * @author 老张
 * @date 2024-05-27
 */
public interface AppUserMapper extends BaseMapperPlus<AppUserMapper, AppUser, AppUserVo> {
    /** 日志 */
    Logger logger = LoggerFactory.getLogger(AppUserMapper.class);

    /**
     * 查询钱包余额
     *
     * @author 老张
     */
    @Select("select gold_balance,diamond_balance from ykj_app_user_wallet WHERE id =#{id}")
    Wallect queryBanlance(@Param("id") Long id);

    /**
     * 查询爱洞ID集合
     *
     * @author 老张
     */
    @Select("<script>" +
        "SELECT  " +
        "platform_id  " +
        "FROM  " +
        "ykj_app_user  " +
        "where account_type = 1  and status = 0  " +
        "<if test='type==2'>" +
        "and sex = 0 " +
        "</if>" +
        "<if test='type==3'>" +
        "and sex = 1 " +
        "</if>" +
        "<if test='type==4'>" +
        "and sex = 0 and online_status <![CDATA[<>]]> 0 " +
        "</if>" +
        "<if test='type==5'>" +
        "and sex = 1 and online_status <![CDATA[<>]]> 0 " +
        "</if>" +
        "<if test='type==1'>" +
        "and online_status <![CDATA[<>]]> 0" +
        "</if>" +
        "</script>")
    List<Long> queryPlatformIds(@Param("type") Integer type);

    /**
     * 添加金币
     *
     * @author 老张
     */
    @Update("UPDATE ykj_app_user_wallet SET gold_balance = (gold_balance+#{num}) WHERE id =#{id}")
    Integer addGoldBalance(@Param("id") Long id, @Param("num") BigDecimal num);
    /**
     * 添加钻石
     *
     * @author 老张
     */
    @Update(
            "UPDATE ykj_app_user_wallet SET diamond_balance = (diamond_balance+#{num}) WHERE id =#{id}")
    Integer addDiamondBalance(@Param("id") Long id, @Param("num") BigDecimal num);
    /**
     * 减少钻石
     *
     * @author 老张
     */
    @Update(
            "UPDATE ykj_app_user_wallet SET diamond_balance = (diamond_balance-#{num}) WHERE id =#{id}")
    Integer subDiamondBalance(@Param("id") Long id, @Param("num") BigDecimal num);
    /**
     * 减少金币
     *
     * @author 老张
     */
    @Update("UPDATE ykj_app_user_wallet SET gold_balance = (gold_balance-#{num}) WHERE id =#{id}")
    Integer subGoldBalance(@Param("id") Long id, @Param("num") BigDecimal num);

    /**
     * 添加亲密度
     *
     * @param id
     */
    @Update("UPDATE ykj_user_focus SET love_value = (love_value+#{num}) WHERE sort_id =#{id}")
    Integer addLoveValue(@Param("id") String id, @Param("num") BigDecimal num);

    /**
     * 用户在线状态 0 离线 1 忙碌 2空闲 重置用户状态
     *
     * @author 老张
     */
    @Update(
        "UPDATE ykj_app_user SET online_status = #{afterStatus} WHERE id = #{uid} and online_status =#{beforeStatus}")
    void resetUserStatus(
        @Param("uid") Long uid,
        @Param("beforeStatus") Integer beforeStatus,
        @Param("afterStatus") Integer afterStatus);

    /**
     * 增加金币-统计累计充值成功次数，累计充值总数
     *
     * @author 老张
     * @param id
     */
    @Update(
        "UPDATE ykj_app_user_wallet SET charge_total = charge_total+#{charge_total},charge_num = charge_num+#{charge_num} WHERE id =#{id}")
    Integer addGoldCount(
        @Param("id") Long id,
        @Param("charge_total") BigDecimal chargeTotal,
        @Param("charge_num") Integer chargeNum);

    /**
     * 钱包对象
     *
     * @author 老张
     */
    @Data
    class Wallect {
        private BigDecimal goldBalance;
        private BigDecimal diamondBalance;

        @Override
        public String toString() {
            return JSONUtil.toJsonStr(this);
        }
    }

    /**
     * 记录添加金币余额的日志
     *
     * @param payLogDTO 支付日志数据传输对象，包含用户ID、变更数量和业务标签等信息
     * @return 返回0表示日志记录流程结束，具体成功与否通过其他方式如事件bus通知
     */
    default Integer logForAddGoldBalance(PayLogDTO payLogDTO) {
        // 提取用户ID和变更数量
        Long id = payLogDTO.getUid();
        BigDecimal num = payLogDTO.getChangeNum();

        // 检查变更数量是否非正
        if (NumberUtil.isLessOrEqual(num, BigDecimal.ZERO)){
            log.error("添加金币余额为0");
            return 0;
        }

        // 提取业务标签和日志类型
        String tag = payLogDTO.getServiceTag();
        PayLogTypeEnums payLogTypeEnums = payLogDTO.getLogType();

        // 通过Spring上下文获取AppUserMapper和LockTemplate
        AppUserMapper appUserMapper = SpringUtil.getBean("appUserMapper");
        LockTemplate lockTemplate = SpringUtil.getBean("lockTemplate");

        // 尝试加锁，防止并发问题
        final LockInfo lockInfo =
                lockTemplate.lock(
                        CacheConstants.LOCK_WALLECT + id,
                        30000L,
                        5000L,
                        RedissonLockExecutor.class);
        if (null == lockInfo) {
            throw new RuntimeException("业务处理中,请稍后再试");
        }

        // 初始化操作结果
        Integer result = 0;
        Wallect wallectBefore = new Wallect();
        Wallect wallectAfter = new Wallect();

        // 尝试执行数据库操作
        try {
            // 查询操作前的余额
            wallectBefore = appUserMapper.queryBanlance(id);
            // 执行增加金币余额操作
            result = appUserMapper.addGoldBalance(id, num);
            // 查询操作后的余额
            wallectAfter = appUserMapper.queryBanlance(id);

            // 记录操作日志
            if (result != 1) {
                logger.error("用户:{},增加金币:{},操作失败:{},业务TAG:{}", id, num, result, tag);
            } else {
                logger.info("用户:{},增加金币:{},操作成功:{},业务TAG:{}", id, num, result, tag);
            }
            return result;
        } catch (Exception e) {
            // 捕获并记录异常
            logger.error("用户:{},增加金币:{},操作失败:{},业务TAG:{}", id, num, result, tag);
            logger.error("操作失败", e);
            return 0;
        } finally {
            // 确保锁被释放
            lockTemplate.releaseLock(lockInfo);
            // 发布支付日志事件
            SpringUtil.publishEvent(
                    PayLogEvent.builder()
                            .traceId(payLogDTO.getTraceId())
                            .logDate(DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN))
                            .userId(id)
                            .logType(payLogTypeEnums.getCode())
                            .symbol("+")
                            .balanceType(BalanceTypeEnums.金币.getCode())
                            .remark(tag)
                            .changeNum(num)
                            .status(
                                    result == 1
                                            ? EnableEnum.YES.getCode()
                                            : EnableEnum.NO.getCode())
                            .walletSnapBefore(wallectBefore.toString())
                            .walletSnapBefore(wallectBefore.toString()) // 这里有重复的字段，应该是笔误
                            .walletSnapAfter(wallectAfter.toString())
                            .build());
        }
    }

    /**
     * 减少用户金币余额并记录日志
     *
     * @param payLogDTO 支付日志DTO，包含用户ID、变更数量、业务标签等信息
     * @return 操作结果，成功返回1，失败返回0
     *
     * 该方法通过用户ID和变更数量来减少用户的金币余额，并记录相应的操作日志
     * 如果减少的金币数量为0或负数，将记录错误日志并返回0或抛出异常
     * 方法使用分布式锁来保证并发操作的安全性
     * 在操作完成后，无论成功还是失败，都会释放锁并发送支付日志事件
     */
    default Integer logSubGoldBalance(PayLogDTO payLogDTO) {
        // 提取用户ID和变更数量
        Long id = payLogDTO.getUid();
        BigDecimal num = payLogDTO.getChangeNum();

        // 检查减少的金币数量是否为0或负数
        if (NumberUtil.isLessOrEqual(num, BigDecimal.ZERO)){
            log.error("减少金币余额为0");
            return 0;
        }

        // 提取业务标签和日志类型
        String tag = payLogDTO.getServiceTag();
        PayLogTypeEnums payLogTypeEnums = payLogDTO.getLogType();

        // 防止传入的金币变更数量小于0
        if (NumberUtil.isGreater(BigDecimal.ZERO, num)) {
            throw new ServiceException("logSubGoldBalance 参数异常");
        }

        // 获取分布式锁模板
        LockTemplate lockTemplate = SpringUtil.getBean("lockTemplate");

        // 尝试加锁，防止并发问题
        final LockInfo lockInfo =
                lockTemplate.lock(
                        CacheConstants.LOCK_WALLECT + id,
                        30000L,
                        5000L,
                        RedissonLockExecutor.class);

        // 如果未成功加锁，抛出运行时异常
        if (null == lockInfo) {
            throw new RuntimeException("业务处理中,请稍后再试");
        }

        // 获取用户信息Mapper
        AppUserMapper appUserMapper = SpringUtil.getBean("appUserMapper");

        // 初始化操作结果变量
        Integer result = 0;

        // 初始化扣款前后的钱包快照
        Wallect wallectBefore = new Wallect();
        Wallect wallectAfter = new Wallect();

        // 尝试减少用户金币余额
        try {
            wallectBefore = appUserMapper.queryBanlance(id);
            result = appUserMapper.subGoldBalance(id, num);
            wallectAfter = appUserMapper.queryBanlance(id);

            // 根据操作结果记录日志
            if (result != 1) {
                logger.error("用户:{},减少金币:{},操作失败:{},业务TAG:{}", id, num, result, tag);
            } else {
                logger.info("用户:{},减少金币:{},操作成功:{},业务TAG:{}", id, num, result, tag);
            }

            // 返回操作结果
            return result;
        } catch (Exception e) {
            // 捕获异常并记录错误日志
            logger.error("用户:{},减少金币:{},操作失败:{},业务TAG:{},msg:{}", id, num, 0, tag, e.getMessage());
            return 0;
        } finally {
            // 无论成功失败，释放锁
            lockTemplate.releaseLock(lockInfo);

            // 构建并发送支付日志事件
            SpringUtil.publishEvent(
                    PayLogEvent.builder()
                            .traceId(payLogDTO.getTraceId())
                            .logDate(DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN))
                            .userId(id)
                            .symbol("-")
                            .changeNum(num)
                            .remark(tag)
                            .balanceType(BalanceTypeEnums.金币.getCode())
                            .logType(payLogTypeEnums.getCode())
                            .status(
                                    result == 1
                                            ? EnableEnum.YES.getCode()
                                            : EnableEnum.NO.getCode())
                            .walletSnapBefore(wallectBefore.toString())
                            .walletSnapAfter(wallectAfter.toString())
                            .build());
        }
    }

    /**
     * 记录用户钻石余额增加日志
     *
     * @param payLogDTO 支付日志DTO，包含用户ID、变更数量、业务标签等信息
     *
     * 该方法用于记录用户钻石余额变动的日志信息。首先检查变更数量是否为正数，若不是，则记录错误并返回。
     * 使用Redisson锁确保并发下操作的原子性。通过Spring框架获取必要的bean实例，使用数据库操作实现余额变更，
     * 并根据操作结果记录相应的日志信息。最后，无论操作是否成功，都会释放Redisson锁，并触发支付日志事件。
     */
    default Integer logAddDiamondBalance(PayLogDTO payLogDTO) {
        // 提取用户ID和变更数量
        Long id = payLogDTO.getUid();
        BigDecimal num = payLogDTO.getChangeNum();

        // 检查变更数量是否为正数
        if (NumberUtil.isLessOrEqual(num, BigDecimal.ZERO)){
            log.error("添加钻石余额为0");
            return 0;
        }

        // 提取业务标签和日志类型
        String tag = payLogDTO.getServiceTag();
        PayLogTypeEnums payLogTypeEnums = payLogDTO.getLogType();

        // 获取数据库操作和锁模板实例
        AppUserMapper appUserMapper = SpringUtil.getBean("appUserMapper");
        LockTemplate lockTemplate = SpringUtil.getBean("lockTemplate");

        // 尝试获取锁，防止并发问题
        final LockInfo lockInfo =
                lockTemplate.lock(
                        CacheConstants.LOCK_WALLECT + id,
                        30000L,
                        5000L,
                        RedissonLockExecutor.class);
        if (null == lockInfo) {
            throw new RuntimeException("业务处理中,请稍后再试");
        }

        // 初始化操作结果
        Integer result = 0;
        Wallect wallectBefore = new Wallect();
        Wallect wallectAfter = new Wallect();

        try {
            // 查询余额变动前的数据
            wallectBefore = appUserMapper.queryBanlance(id);

            // 执行钻石余额增加操作
            result = appUserMapper.addDiamondBalance(id, num);

            // 查询余额变动后的数据
            wallectAfter = appUserMapper.queryBanlance(id);

            // 根据操作结果记录日志
            if (result != 1) {
                logger.error("用户:{},增加钻石:{},操作失败:{},业务TAG:{}", id, num, result, tag);
            } else {
                logger.info("用户:{},增加钻石:{},操作成功:{},业务TAG:{}", id, num, result, tag);
            }
            return result;
        } catch (Exception e) {
            // 捕获并记录操作异常
            logger.error("用户:{},增加钻石:{},操作失败:{},业务TAG:{}", id, num, result, tag);
            logger.error("操作失败", e);
            return 0;
        } finally {
            // 释放锁
            lockTemplate.releaseLock(lockInfo);

            // 构建并发布支付日志事件
            SpringUtil.publishEvent(
                    PayLogEvent.builder()
                            .traceId(payLogDTO.getTraceId())
                            .logDate(DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN))
                            .userId(id)
                            .symbol("+")
                            .changeNum(num)
                            .remark(tag)
                            .balanceType(BalanceTypeEnums.钻石.getCode())
                            .logType(payLogTypeEnums.getCode())
                            .status(
                                    result == 1
                                            ? EnableEnum.YES.getCode()
                                            : EnableEnum.NO.getCode())
                            .walletSnapBefore(wallectBefore.toString())
                            .walletSnapAfter(wallectAfter.toString())
                            .build());
        }
    }

    /**
     * 减少用户钻石余额
     *
     * @param payLogDTO 支付日志DTO，包含用户ID、变更数量、服务标签、日志类型等信息
     * @return 减少钻石余额操作的结果，成功返回1，否则返回0
     */
    default Integer logSubDiamondBalance(PayLogDTO payLogDTO) {
        // 获取用户ID
        Long id = payLogDTO.getUid();
        // 获取钻石变更数量
        BigDecimal num = payLogDTO.getChangeNum();

        // 检查变更数量是否小于等于0，是则记录错误并返回0
        if (NumberUtil.isLessOrEqual(num, BigDecimal.ZERO)) {
            log.error("减少钻石余额为0");
            return 0;
        }

        // 获取服务标签和支付日志类型
        String tag = payLogDTO.getServiceTag();
        PayLogTypeEnums payLogTypeEnums = payLogDTO.getLogType();

        // 检查变更数量是否小于0，是则抛出服务异常
        if (NumberUtil.isGreater(BigDecimal.ZERO, num)) {
            throw new ServiceException("logSubDiamondBalance 参数异常");
        }

        // 获取锁模板，用于分布式锁的处理
        LockTemplate lockTemplate = SpringUtil.getBean("lockTemplate");
        // 尝试加锁，防止并发问题
        final LockInfo lockInfo =
                lockTemplate.lock(
                        CacheConstants.LOCK_WALLECT + id,
                        30000L,
                        5000L,
                        RedissonLockExecutor.class);
        // 如果未成功加锁，抛出运行时异常，提示业务处理中
        if (null == lockInfo) {
            throw new RuntimeException("业务处理中,请稍后再试");
        }

        // 初始化操作结果为0，默认认为操作失败
        Integer result = 0;
        Wallect wallectBefore = new Wallect();
        Wallect wallectAfter = new Wallect();

        // 事务开始，尝试减少用户钻石余额
        try {
            // 获取用户余额Mapper
            AppUserMapper appUserMapper = SpringUtil.getBean("appUserMapper");
            // 查询用户当前余额
            wallectBefore = appUserMapper.queryBanlance(id);
            // 执行减少钻石余额操作
            result = appUserMapper.subDiamondBalance(id, num);
            // 再次查询用户余额
            wallectAfter = appUserMapper.queryBanlance(id);

            // 如果操作失败，记录错误日志
            if (result != 1) {
                logger.error("用户:{},减少钻石:{},操作失败:{},业务TAG:{}", id, num, result, tag);
            } else {
                // 如果操作成功，记录成功日志
                logger.info("用户:{},减少钻石:{},操作成功:{},业务TAG:{}", id, num, result, tag);
            }
            // 返回操作结果
            return result;
        } catch (Exception e) {
            // 如果发生异常，记录错误日志
            logger.error("用户:{},减少钻石:{},操作失败:{},业务TAG:{},msg:{}", id, num, 0, tag, e.getMessage());
            // 返回失败结果
            return 0;
        } finally {
            // 释放锁
            lockTemplate.releaseLock(lockInfo);
            // 发布支付日志事件，记录变更前后的钱包快照
            SpringUtil.publishEvent(
                    PayLogEvent.builder()
                            .traceId(payLogDTO.getTraceId())
                            .logDate(DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN))
                            .userId(id)
                            .symbol("-")
                            .changeNum(num)
                            .remark(tag)
                            .balanceType(BalanceTypeEnums.钻石.getCode())
                            .logType(payLogTypeEnums.getCode())
                            .status(
                                    result == 1
                                            ? EnableEnum.YES.getCode()
                                            : EnableEnum.NO.getCode())
                            .walletSnapBefore(wallectBefore.toString())
                            .walletSnapAfter(wallectAfter.toString())
                            .build());
        }
    }


    @Select("<script>" +
        "select * from (SELECT " +
        "DATE_FORMAT( create_time, '%Y-%m' ) days, " +
        "count( 1 ) reg_count  " +
        "FROM " +
        "ykj_app_user  " +
        "WHERE " +
        "branch = #{branch}  " +
        "GROUP BY " +
        "days ) as t1 " +
        "where t1.days <![CDATA[>=]]> #{startDate} " +
        "and t1.days <![CDATA[<=]]> #{endDate} order by days asc " +
        "</script>")
    Page<IndexVo> queryRegisterCount(@Param("startDate") String startDate,
                                     @Param("endDate") String endDate,
                                     @Param("branch") Integer branch,
                                     @Param("Page") Page<CallSheetInfo> Page);
}
